from abc import ABCMeta, abstractmethod
from collections import namedtuple

from six import add_metaclass


class BlobDescriptor(namedtuple("Blob", ["mediaType", "size", "digest", "urls"])):
    """
    BlobDescriptor describes a blob with its mediatype, size and digest.

    A BlobDescriptor is used to retrieves the actual blob.
    """


class ChannelReleasesView(namedtuple("ChannelReleasesView", ["name", "current", "releases"])):
    """
    A channel is a pointer to a Release (current).

    Releases are the previous tags pointed by channel (history).
    """


class ChannelView(namedtuple("ChannelView", ["name", "current"])):
    """
    A channel is a pointer to a Release (current).
    """


class ApplicationSummaryView(
    namedtuple(
        "ApplicationSummaryView",
        [
            "name",
            "namespace",
            "visibility",
            "default",
            "manifests",
            "channels",
            "releases",
            "updated_at",
            "created_at",
        ],
    )
):
    """
    ApplicationSummaryView is an aggregated view of an application repository.
    """


class ApplicationManifest(namedtuple("ApplicationManifest", ["mediaType", "digest", "content"])):
    """
    ApplicationManifest embed the BlobDescriptor and some metadata around it.

    An ApplicationManifest is content-addressable.
    """


class ApplicationRelease(
    namedtuple("ApplicationRelease", ["release", "name", "created_at", "manifest"])
):
    """
    The ApplicationRelease associates an ApplicationManifest to a repository and release.
    """


@add_metaclass(ABCMeta)
class AppRegistryDataInterface(object):
    """
    Interface that represents all data store interactions required by a App Registry.
    """

    @abstractmethod
    def list_applications(
        self, namespace=None, media_type=None, search=None, username=None, with_channels=False
    ):
        """
        Lists all repositories that contain applications, with optional filtering to a specific
        namespace and/or to those visible to a specific user.

        Returns: list of ApplicationSummaryView
        """
        pass

    @abstractmethod
    def application_is_public(self, package_name):
        """
        Returns true if the application is public.
        """
        pass

    @abstractmethod
    def create_application(self, package_name, visibility, owner):
        """
        Create a new app repository, owner is the user who creates it.
        """
        pass

    @abstractmethod
    def application_exists(self, package_name):
        """
        Returns true if the application exists.
        """
        pass

    @abstractmethod
    def basic_search(self, query, username=None):
        """ Returns an array of matching application in the format: 'namespace/name'
    Note:
      * Only 'public' repositories are returned
    """
        pass

    # @TODO: Paginate
    @abstractmethod
    def list_releases(self, package_name, media_type=None):
        """ Returns the list of all releases(names) of an AppRepository
    Example:
        >>> get_app_releases('ant31/rocketchat')
        ['1.7.1', '1.7.0', '1.7.2']
    """
        pass

    # @TODO: Paginate
    @abstractmethod
    def list_manifests(self, package_name, release=None):
        """
        Returns the list of all available manifests type of an Application across all releases or
        for a specific one.

    Example:
        >>> get_app_releases('ant31/rocketchat')
        ['1.7.1', '1.7.0', '1.7.2']
        """
        pass

    @abstractmethod
    def fetch_release(self, package_name, release, media_type):
        """
        Returns an ApplicationRelease.
        """
        pass

    @abstractmethod
    def store_blob(self, cnrblob, content_media_type):
        """
        Upload the blob content to a storage location and creates a Blob entry in the DB.

        Returns a BlobDescriptor
        """
        pass

    @abstractmethod
    def create_release(self, package, user, visibility, force=False):
        """
        Creates and returns an ApplicationRelease.

        - package is a data.model.Package object
        - user is the owner of the package
        - visibility is a string: 'public' or 'private'
        """
        pass

    @abstractmethod
    def release_exists(self, package, release):
        """
        Return true if a release with that name already exist or has existed (including deleted
        ones)
        """
        pass

    @abstractmethod
    def delete_release(self, package_name, release, media_type):
        """
        Remove/Delete an app-release from an app-repository.

        It does not delete the entire app-repository, only a single release
        """
        pass

    @abstractmethod
    def list_release_channels(self, package_name, release, active=True):
        """
        Returns a list of Channel that are/was pointing to a release.

        If active is True, returns only active Channel (lifetime_end not null)
        """
        pass

    @abstractmethod
    def channel_exists(self, package_name, channel_name):
        """
        Returns true if the channel with the given name exists under the matching package.
        """
        pass

    @abstractmethod
    def update_channel(self, package_name, channel_name, release):
        """
        Append a new release to the Channel Returns a new Channel with the release as current.
        """
        pass

    @abstractmethod
    def delete_channel(self, package_name, channel_name):
        """
        Delete a Channel, it doesn't delete/touch the ApplicationRelease pointed by the channel.
        """

    # @TODO: Paginate
    @abstractmethod
    def list_channels(self, package_name):
        """
        Returns all AppChannel for a package.
        """
        pass

    @abstractmethod
    def fetch_channel(self, package_name, channel_name, with_releases=True):
        """ Returns an Channel
    Raises: ChannelNotFound, PackageNotFound
    """
        pass

    @abstractmethod
    def log_action(
        self,
        event_name,
        namespace_name,
        repo_name=None,
        analytics_name=None,
        analytics_sample=1,
        **kwargs
    ):
        """
        Logs an action to the audit log.
        """
        pass

    @abstractmethod
    def get_blob_locations(self, digest):
        """
        Returns a list of strings for the locations in which a Blob is present.
        """
        pass
